﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.ServiceModel;

namespace WCFPeerResolver
{
    /// <summary>
    /// Represents a collection of MeshEntries grouped into a mesh.
    /// </summary>
    public class Mesh : IEnumerable<MeshEntry>
    {
        /// <summary>
        /// Idenfifier of the mesh.
        /// </summary>
        public readonly string MeshID;

        private readonly Dictionary<Guid, MeshEntry> m_Entries = new Dictionary<Guid, MeshEntry>();

        /// <summary>
        /// The time of the last refresh call. Used to determine if the mesh is dead and should be removed.
        /// </summary>
        public DateTime Updated { get; private set; }

        /// <summary>
        /// Constructor.
        /// </summary>
        /// <param name="meshID">ID of the mesh.</param>
        public Mesh(string meshID)
        {
            MeshID = meshID;
        }

        /// <summary>
        /// Add a new entry to the mesh.
        /// </summary>
        /// <param name="entry">Entry to add.</param>
        public void Add(MeshEntry entry)
        {
            m_Entries[entry.RegistrationID] = entry;
        }

        /// <summary>
        /// Update an existing entry.
        /// </summary>
        /// <param name="entry">Entry to update.</param>
        public void Update(MeshEntry entry)
        {
            m_Entries[entry.RegistrationID] = entry;
        }

        /// <summary>
        /// Get all registred peer node addresses.
        /// </summary>
        /// <param name="max"></param>
        /// <returns></returns>
        public PeerNodeAddress[] GetRegistredPeerNodes(int max)
        {
            PeerNodeAddress[] endPoints;
            
            if (max <= m_Entries.Count)
            {
                endPoints = (from entry in m_Entries.Values
                             select entry.Address).ToArray();
            }
            else
            {
                Random rnd = new Random();
                endPoints = (from entry in m_Entries.Values
                             select entry.Address).OrderBy(p => rnd.Next()).Take(max).ToArray();
            }
            return endPoints;
        }

        /// <summary>
        /// Remove an entry.
        /// </summary>
        /// <param name="registrationID">ID of the entry to remove.</param>
        internal void Remove(Guid registrationID)
        {
            m_Entries.Remove(registrationID);
        }

        /// <summary>
        /// Refresh the Updated values of the mesh and entry if it exists.
        /// </summary>
        /// <param name="registrationID">ID of the entry.</param>
        /// <param name="time">Time of update.</param>
        /// <returns>True if the entry was found and updated, False otherwise.</returns>
        internal bool Refresh(Guid registrationID, DateTime time)
        {
            Updated = time;

            MeshEntry entry;

            if (m_Entries.TryGetValue(registrationID, out entry))
            {
                entry.Refresh(time);
                return true;
            }
            return false;
        }

        /// <summary>
        /// Clean up dead meshes and mesh entries.
        /// </summary>
        /// <remarks>This method is for internal use only.</remarks>
        /// <param name="time"></param>
        /// <param name="cleanupInterval"></param>
        internal void Cleanup(DateTime time, TimeSpan cleanupInterval)
        {
            Guid[] records = m_Entries.Where(p => time - p.Value.Updated > cleanupInterval).Select(p => p.Key).ToArray();

            foreach (Guid record in records)
            {
                m_Entries.Remove(record);
            }
        }

        /// <summary>
        /// Clone a mesh.
        /// </summary>
        /// <returns>A cloned mesh.</returns>
        public Mesh Clone()
        {
            Mesh clone = new Mesh(MeshID);

            foreach (KeyValuePair<Guid, MeshEntry> entry in m_Entries)
            {
                clone.m_Entries.Add(entry.Key, entry.Value.Clone());
            }

            return clone;
        }

        #region IEnumerable<MeshEntry> Members

        public IEnumerator<MeshEntry> GetEnumerator()
        {
            return m_Entries.Values.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}